﻿#region License

// PentaBiz - Sustainable Software Development Framework Copyright (C) 2013 Zoltán Csizmazia
// 
// This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

#endregion

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Services.Providers;
using System.Diagnostics;
using System.Linq;
using PentaBiz.Cloud.CloudDb;
using PentaBiz.Collections;

namespace PentaBiz.Cloud.Services.QueryService.OData
{
    public class ODataServiceMetadataProvider : IDataServiceMetadataProvider
    {
        private readonly List<ResourceAssociationSet> _associationSets = new List<ResourceAssociationSet>();


        private readonly string _containerName = "Container";
        private readonly string _containerNamespace = "Namespace";

        private readonly ConcurrentDictionary<ResourceType, List<ResourceType>> derivedTypesLookup =
            new ConcurrentDictionary<ResourceType, List<ResourceType>>();

        private readonly Dictionary<string, ResourceSet> resourceSets = new Dictionary<string, ResourceSet>();
        private readonly Dictionary<string, ResourceType> resourceTypes = new Dictionary<string, ResourceType>();

        public ODataServiceMetadataProvider()
        {
        }

        public ODataServiceMetadataProvider(string containerName, string containerNamespace)
        {
            _containerName = containerName;
            _containerNamespace = containerNamespace;
        }

        #region IDataServiceMetadataProvider Members

        public string ContainerName
        {
            get { return _containerName; }
        }

        public string ContainerNamespace
        {
            get { return _containerNamespace; }
        }

        public IEnumerable<ResourceSet> ResourceSets
        {
            get { return resourceSets.Values; }
        }

        public IEnumerable<ServiceOperation> ServiceOperations
        {
            get { yield break; }
        }

        public IEnumerable<ResourceType> Types
        {
            get { return resourceTypes.Values; }
        }

        public bool TryResolveResourceSet(string name, out ResourceSet resourceSet)
        {
            return resourceSets.TryGetValue(name, out resourceSet);
        }

        public bool TryResolveResourceType(string name, out ResourceType resourceType)
        {
            return resourceTypes.TryGetValue(name, out resourceType);
        }

        public bool TryResolveServiceOperation(string name, out ServiceOperation serviceOperation)
        {
            serviceOperation = null;
            return false;
        }

        public bool HasDerivedTypes(ResourceType resourceType)
        {
            return derivedTypesLookup.ContainsKey(resourceType);
        }

        public IEnumerable<ResourceType> GetDerivedTypes(ResourceType resourceType)
        {
            return derivedTypesLookup[resourceType];
        }

        public ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType,
                                                                ResourceProperty resourceProperty)
        {
            return resourceProperty.CustomState as ResourceAssociationSet;
        }

        #endregion

        public void AddResourceType(ResourceType type)
        {
            type.SetReadOnly();
            resourceTypes.Add(type.FullName, type);
        }

        public void AddDerivedResourceType(ResourceType type, ResourceType derivedResourceType)
        {
            derivedResourceType.SetReadOnly();

            derivedTypesLookup.AddOrUpdate(type,
                                           new List<ResourceType>(new[] {derivedResourceType}),
                                           (resourceType, list) =>
                                               {
                                                   list.Add(derivedResourceType);
                                                   return list;
                                               });
        }

        public void AddResourceSet(ResourceSet set)
        {
            set.SetReadOnly();
            resourceSets.Add(set.Name, set);
        }

        public void AddAssociationSet(ResourceAssociationSet associationSet)
        {
            _associationSets.Add(associationSet);
        }

        public static ODataServiceMetadataProvider Create(Type dataSourceType)
        {
            var metadata = new ODataServiceMetadataProvider(dataSourceType.Name, dataSourceType.Namespace);


            //Resource types of a CLR type
            var resourceTypes = new Dictionary<Type, ResourceType>();

            //resource sets of a CLR type
            var resourceSets = new Dictionary<Type, ResourceSet>();
            //complex types
            var complexTypes = new Dictionary<Type, ResourceType>();

            //derivedResourceTypes of a ResourceType
            var derivedResources = new Dictionary<ResourceType, List<ResourceType>>();

            //referenced resource types from a resourcetype (via properties)
            var resourceSetReferences = new Cache<ResourceType, List<ResourceType>>();
            var resourceAssociationSets = new List<ResourceAssociationSet>();

            //actual property to base ResourceType lookup
            var inheritanceLookup = new Dictionary<Type, ResourceType>();

            //matching resource set for a specified resource type
            var resourceSetsForResourceTypes = new Dictionary<ResourceType, ResourceSet>();

            //all resource type lokkup for a specified CLR type
            var allResourceTypesForInstanceType = new Dictionary<Type, ResourceType>();

            //var entities = dataSourceType.ReflectIQueryableProperties();
            var metaDataEntries = (from a in dataSourceType.ReflectIQueryableProperties(true)
                                   let entityType = a.PropertyType.GetGenericArguments()[0]
                                   let propertyType = a.PropertyType
                                   let propertyName = a.Name
                                   //let resourceTypeKind = entityType.IsEntityType() ? ResourceTypeKind.EntityType : ResourceTypeKind.ComplexType
                                   let resourceType =
                                       new ResourceType(entityType, ResourceTypeKind.EntityType, null,
                                                        entityType.Namespace, entityType.Name, propertyType.IsAbstract)
                                   let resourceSet = new ResourceSet(propertyName, resourceType)
                                   select new
                                       {
                                           EntityPropertyName = propertyName,
                                           EntityPropertyType = propertyType,
                                           EntityType = entityType,
                                           EntityResourceTypeKind = ResourceTypeKind.EntityType,
                                           EntityResourceType = resourceType,
                                           EntityResourceSet = resourceSet,
                                           EntityProperties = (from entityProperty in entityType.GetProperties()
                                                               where !entityProperty.PropertyType.IsEnum
                                                               select new
                                                                   {
                                                                       PropertyName = entityProperty.Name,
                                                                       entityProperty.PropertyType,
                                                                       IsKey =
                                                                   entityProperty.IsEntityKeyProperty(),
                                                                       IsCollection =
                                                                   entityProperty.IsCollectionProperty(),
                                                                   }).ToArray(),
                                       }
#if DEBUG
                                   into r
                                   orderby r.EntityPropertyName
                                   select r
#endif
                                  ).ToArray();

            //populate entity metadata
            foreach (var metaDataEntry in metaDataEntries)
            {
                ResourceType entityResourceType = metaDataEntry.EntityResourceType;
                ResourceSet entityResourceSet = metaDataEntry.EntityResourceSet;

                resourceTypes.Add(metaDataEntry.EntityType, entityResourceType);
                resourceSets.Add(metaDataEntry.EntityType, entityResourceSet);


                resourceSetsForResourceTypes.Add(entityResourceType, entityResourceSet);
                allResourceTypesForInstanceType.Add(entityResourceType.InstanceType, entityResourceType);

                //try find derived types
                List<ResourceType> derivedResourceTypes =
                    (from a in metaDataEntry.EntityType.ReflectDerivedTypes(AppDomain.CurrentDomain.GetAssemblies())
                     select new ResourceType(a, ResourceTypeKind.EntityType, entityResourceType,
                                             a.Namespace, a.Name, a.IsAbstract)).ToList();

                if (derivedResourceTypes.Any())
                {
                    derivedResources.Add(entityResourceType, derivedResourceTypes);

                    foreach (var key in derivedResourceTypes.Select(c =>
                                                                    new
                                                                        {
                                                                            c.InstanceType
                                                                        }))
                    {
                        inheritanceLookup.Add(key.InstanceType, entityResourceType);
                    }

                    foreach (var resourceType in derivedResourceTypes)
                    {
                        resourceSetsForResourceTypes.Add(resourceType, entityResourceSet);
                        allResourceTypesForInstanceType.Add(resourceType.InstanceType, entityResourceType);
                    }
                }
            }

            //populate entity properties
            foreach (var metaDataEntry in metaDataEntries)
            {
                ResourceType entityResourceType = metaDataEntry.EntityResourceType;
                ResourceSet entityResourceSet = metaDataEntry.EntityResourceSet;

                //populate entity properties
                foreach (var metaPropertyEntry in metaDataEntry.EntityProperties)
                {
                    //foreach entity property

                    //build resource property kind
                    ResourcePropertyKind rpKind;

                    #region Check for ResourcePropertyKind

                    bool isAssociation = resourceTypes.ContainsKey(metaPropertyEntry.PropertyType);

                    if (metaPropertyEntry.IsCollection)
                        rpKind = ResourcePropertyKind.ResourceSetReference;
                    else if (isAssociation)
                        rpKind = ResourcePropertyKind.ResourceReference;
                    else
                    {
                        //check for complex type
                        if (!metaPropertyEntry.PropertyType.FullName.StartsWith("System"))
                        {
                            if (!complexTypes.ContainsKey(metaPropertyEntry.PropertyType))
                                complexTypes.Add(metaPropertyEntry.PropertyType,
                                                 new ResourceType(metaPropertyEntry.PropertyType,
                                                                  ResourceTypeKind.ComplexType, null,
                                                                  metaPropertyEntry.PropertyType.Namespace,
                                                                  metaPropertyEntry.PropertyType.Name,
                                                                  metaPropertyEntry.PropertyType.IsAbstract));
                            rpKind = ResourcePropertyKind.ComplexType;
                        }
                        else
                        {
                            rpKind = ResourcePropertyKind.Primitive;
                        }
                    }

                    //check if meta property is Key
                    if (metaPropertyEntry.IsKey)
                        rpKind |= ResourcePropertyKind.Key;

                    #endregion

                    ResourceType rpResourceType = null;

                    #region Build ResourceType

                    if ((rpKind & ResourcePropertyKind.Primitive) == ResourcePropertyKind.Primitive)
                        rpResourceType = ResourceType.GetPrimitiveResourceType(metaPropertyEntry.PropertyType);
                    else if (rpKind == ResourcePropertyKind.ComplexType)
                    {
                        rpResourceType = complexTypes[metaPropertyEntry.PropertyType];
                    }
                    else if (rpKind == ResourcePropertyKind.ResourceReference)
                    {
                        rpResourceType = resourceTypes[metaPropertyEntry.PropertyType];
                    }
                    else if (rpKind == ResourcePropertyKind.ResourceSetReference)
                    {
                        var resourceType = metaPropertyEntry.PropertyType.GetGenericArguments()[0];
                        //Debug.Assert(resourceTypes.ContainsKey(resourceType), "Resource type for ResourceSetReference is not present");


                        //this case the property resource type is non inherited entity type
                        rpResourceType = allResourceTypesForInstanceType[resourceType];


                        Debug.Assert(rpResourceType != null, "Resource type for the property cannot be null");
                        resourceSetReferences.AddOrUpdate(entityResourceType,
                                                          () => new List<ResourceType>(new[] {rpResourceType}),
                                                          (type, list) =>
                                                              {
                                                                  list.Add(rpResourceType);
                                                                  return list;
                                                              }
                            );
                    }
                    else
                    {
                        throw new InvalidOperationException("rpKind is not handled!");
                    }

                    #endregion

                    //create resource property for the EntityProperty
                    var resourceProperty = new ResourceProperty(metaPropertyEntry.PropertyName, rpKind, rpResourceType);

                    //add resource property to the entity resource type
                    entityResourceType.AddProperty(resourceProperty);
                }
            }
            //todo: implement ResourceAssociationSet
            Debug.WriteLine(
                "TODO: Should have implemented the ResourceAssociationSet!!!. Iterate collections, check for opposite association, and finally register association pairs");


            foreach (var entry in resourceSetReferences)
            {
                foreach (ResourceType resourceType in entry.Value)
                {
                    //var oppositeAssociation = resourceType.Properties.SingleOrDefault(r => r.ResourceType == entry.Key);

                    //if(oppositeAssociation!=null)
                    //{
                    //var collectionSet = resourceSets[entry.Key.InstanceType];
                    //var associationSet = resourceSets[oppositeAssociation.]
                    //var r = new ResourceAssociationSet(resourceType.Name,
                    //    new ResourceAssociationSetEnd(collectionSet,), );

                    //resourceAssociationSets.Add();
                    //}
                }
            }

            //ResourceAssociationSet customerUserListSet = new ResourceAssociationSet(
            //    "CustomerUserList",
            //    new ResourceAssociationSetEnd(
            //        customerSet,
            //        customer,
            //        customerUserList
            //    ),
            //    new ResourceAssociationSetEnd(
            //        userSet,
            //        user,
            //        userCustomer
            //    )
            //);
            //userCustomer.CustomState = customerUserListSet;
            //customerUserList.CustomState = customerUserListSet;

            foreach (var resourceType in resourceTypes)
                metadata.AddResourceType(resourceType.Value);

            foreach (var resourceType in derivedResources)
                foreach (ResourceType derivedResourceType in resourceType.Value)
                    metadata.AddDerivedResourceType(resourceType.Key, derivedResourceType);

            foreach (var resourceType in complexTypes)
                metadata.AddResourceType(resourceType.Value);

            foreach (var resourceSet in resourceSets)
                metadata.AddResourceSet(resourceSet.Value);

            return metadata;
        }
    }

    //public class ODataServiceUpdateProvider<T> : IDataServiceUpdateProvider where T : IODataContext
    //{
    //    private IDataServiceMetadataProvider _metadata;
    //    private ODataServiceQueryProvider<T> _query;
    //    private List<Action> _actions;

    //    public T GetContext()
    //    {
    //        return ((T)_query.CurrentDataSource);
    //    }

    //    public void SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues)
    //    {
    //        throw new NotImplementedException();
    //    }

    //    public void SetReference(object targetResource, string propertyName, object propertyValue)
    //    {
    //        _actions.Add(() => ReallySetReference(targetResource, propertyName, propertyValue));
    //    }

    //    public void ReallySetReference(object targetResource, string propertyName, object propertyValue)
    //    {
    //        targetResource.SetPropertyValue(propertyName, propertyValue);
    //    }

    //    public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
    //    {
    //        _actions.Add(() => ReallyAddReferenceToCollection(targetResource, propertyName, resourceToBeAdded));
    //    }

    //    public void ReallyAddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
    //    {
    //        var collection = targetResource.GetPropertyValue(propertyName);
    //        if (collection is IList)
    //        {
    //            (collection as IList).Add(resourceToBeAdded);
    //        }
    //    }

    //    public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
    //    {
    //        _actions.Add(() => ReallyRemoveReferenceFromCollection(targetResource, propertyName, resourceToBeRemoved));
    //    }

    //    public void ReallyRemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
    //    {
    //        var collection = targetResource.GetPropertyValue(propertyName);
    //        if (collection is IList)
    //        {
    //            (collection as IList).Remove(resourceToBeRemoved);
    //        }
    //    }

    //    public void ClearChanges()
    //    {
    //        _actions.Clear();
    //    }

    //    public void SaveChanges()
    //    {
    //        foreach (var a in _actions)
    //            a();
    //        GetContext().SaveChanges();
    //    }

    //    public object CreateResource(string containerName, string fullTypeName)
    //    {
    //        ResourceType type = null;
    //        if (_metadata.TryResolveResourceType(fullTypeName, out type))
    //        {
    //            var context = GetContext();
    //            var resource = context.CreateResource(type);
    //            _actions.Add(() => context.AddResource(type, resource));
    //            return resource;
    //        }
    //        throw new Exception(string.Format("Type {0} not found", fullTypeName));
    //    }

    //    public void DeleteResource(object targetResource)
    //    {
    //        _actions.Add(() => GetContext().DeleteResource(targetResource));
    //    }

    //    public object GetResource(IQueryable query, string fullTypeName)
    //    {
    //        var enumerator = query.GetEnumerator();
    //        if (!enumerator.MoveNext())
    //            throw new Exception("Resource not found");
    //        var resource = enumerator.Current;
    //        if (enumerator.MoveNext())
    //            throw new Exception("Resource not uniquely identified");

    //        if (fullTypeName != null)
    //        {
    //            ResourceType type = null;
    //            if (!_metadata.TryResolveResourceType(fullTypeName, out type))
    //                throw new Exception("ResourceType not found");
    //            if (!type.InstanceType.IsAssignableFrom(resource.GetType()))
    //                throw new Exception("Unexpected resource type");
    //        }
    //        return resource;
    //    }

    //    public object ResetResource(object resource)
    //    {
    //        _actions.Add(() => ReallyResetResource(resource));
    //        return resource;
    //    }

    //    public void ReallyResetResource(object resource)
    //    {
    //        var clrType = resource.GetType();
    //        ResourceType resourceType = _metadata.Types.Single(t => t.InstanceType == clrType);
    //        var resetTemplate = GetContext().CreateResource(resourceType);

    //        foreach (var prop in resourceType.Properties
    //                 .Where(p => (p.Kind & ResourcePropertyKind.Key) != ResourcePropertyKind.Key))
    //        {
    //            var clrProp = clrType.GetProperties().Single(p => p.Name == prop.Name);
    //            var defaultPropValue = clrProp.GetGetMethod().Invoke(resetTemplate, new object[] { });
    //            clrProp.GetSetMethod().Invoke(resource, new object[] { defaultPropValue });
    //        }
    //    }

    //    public object ResolveResource(object resource)
    //    {
    //        return resource;
    //    }

    //    public object GetValue(object targetResource, string propertyName)
    //    {
    //        var value = targetResource.GetType().GetProperties().Single(p => p.Name == propertyName).GetGetMethod().Invoke(targetResource, new object[] { });
    //        return value;
    //    }

    //    public void SetValue(object targetResource, string propertyName, object propertyValue)
    //    {
    //        targetResource.GetType().GetProperties().Single(p => p.Name == propertyName).GetSetMethod().Invoke(targetResource, new[] { propertyValue });
    //    }

    //    public ODataServiceUpdateProvider(IDataServiceMetadataProvider metadata, ODataServiceQueryProvider<T> query)
    //    {
    //        _metadata = metadata;
    //        _query = query;
    //        _actions = new List<Action>();
    //    }
    //}

    //public partial class MyDataContext : IODataContext
    //{
    //    private List<Customer> _customers = null;
    //    public List<Customer> Customers
    //    {
    //        get
    //    {
    //        if (_customers == null)
    //        {
    //            _customers = DataManager.GetCustomers);
    //        }
    //        return _customers;
    //    }
    //    }

    //    private List<ResidentialCustomer> _residentialCustomers = null;
    //    public List<ResidentialCustomer> ResidentialCustomers
    //    {
    //        get
    //        {
    //            if (_residentialCustomers == null)
    //            {
    //                _residentialCustomers = DataManager.GetResidentialCustomers();
    //            }
    //            return _residentialCustomers;
    //        }
    //    }

    //    private List<User> _users = null;
    //    public List<User> Users
    //    {
    //        get
    //        {
    //            if (_users == null)
    //            {
    //                _users = DataManager.GetUsers();
    //            }
    //            return _users;
    //        }
    //    }

    //    public IQueryable GetQueryable(ResourceSet set)
    //    {
    //        if (set.Name == "Customers") return Customers.AsQueryable();
    //        if (set.Name == "ResidentialCustomers") return ResidentialCustomers.AsQueryable();
    //        if (set.Name == "Users") return Users.AsQueryable();
    //        throw new NotSupportedException(string.Format("{0} not found", set.Name));
    //    }

    //    //public object CreateResource(ResourceType resourceType)
    //    //{
    //    //    if (resourceType.InstanceType == typeof(Customer))
    //    //    {
    //    //        return new Customer();
    //    //    }
    //    //    if (resourceType.InstanceType == typeof(ResidentialCustomer))
    //    //    {
    //    //        return new ResidentialCustomer();
    //    //    }
    //    //    if (resourceType.InstanceType == typeof(User))
    //    //    {
    //    //        return new User();
    //    //    }
    //    //    throw new NotSupportedException(string.Format("{0} not found for creating.", resourceType.FullName));
    //    //}

    //    //public void AddResource(ResourceType resourceType, object resource)
    //    //{
    //    //    if (resourceType.InstanceType == typeof(Customer))
    //    //    {
    //    //        Customer i = resource as Customer;
    //    //        if (i != null)
    //    //        {
    //    //            Customers.Add(i);
    //    //            return;
    //    //        }
    //    //    }
    //    //    if (resourceType.InstanceType == typeof(ResidentialCustomer))
    //    //    {
    //    //        ResidentialCustomeri = resource as ResidentialCustomer;
    //    //        if (i != null)
    //    //        {
    //    //            ResidentialCustomers.Add(i);
    //    //            return;
    //    //        }
    //    //    }
    //    //    if (resourceType.InstanceType == typeof(User))
    //    //    {
    //    //        Useri = resource as User;
    //    //        if (i != null)
    //    //        {
    //    //            Users.Add(i);
    //    //            return;
    //    //        }
    //    //    }
    //    //    throw new NotSupportedException(string.Format("{0} not found for adding.", resourceType.FullName));
    //    //}

    //    //public void DeleteResource(object resource)
    //    //{
    //    //    if (resource.GetType() == typeof(Customer))
    //    //    {
    //    //        Customers.Remove(resource as Customer);
    //    //        return;
    //    //    }
    //    //    if (resource.GetType() == typeof(ResidentialCustomer))
    //    //    {
    //    //        ResidentialCustomers.Remove(resource as ResidentialCustomer);
    //    //        return;
    //    //    }
    //    //    if (resource.GetType() == typeof(User))
    //    //    {
    //    //        Users.Remove(resource as User);
    //    //        return;
    //    //    }
    //    //    throw new NotSupportedException(string.Format("{0} not found for deletion.", resource.GetType().FullName));
    //    //}

    //    //public void SaveChanges()
    //    //{
    //    //    foreach (var item in Customers.Where(i => i.IsModified == true))
    //    //        item.Save();
    //    //    foreach (var item in ResidentialCustomers.Where(i => i.IsModified == true))
    //    //        item.Save();
    //    //    foreach (var item in Users.Where(i => i.IsModified == true))
    //    //        item.Save();
    //    //}
    //}
}